﻿/* BorderlessWindowBehavior.cs
 * 
 * Description:
 *
 * Ever wanted to make a Window that looked like the Zune Window?  
 * If so you might have done a "AllowsTransparency=true" and add a pixel shader for a shadow.  
 * The problem is you lose performance in your entire application when you do this and Aero Snap will stop working.  
 * Of course you could just set the border style to none, but then the Windows Aero shadow is gone!  
 * This behavior should give you the same window look as Zune without losing anything to performance.
 *
 * Special thanks to: jmorrill
 *
 * Published: 2010/10/18
 * License: This contribution is licensed to you under Creative Commons by its owner, not Microsoft. 
 *          Microsoft does not guarantee the contribution or purport to grant rights to it.
 *          
 * Found on: http://gallery.expression.microsoft.com/ZuneWindowBehavior
 * Supporting URL: http://jeremiahmorrill.com/
 *
 * Additional Contributions:
 * Incorporated ResizeWithGrip dependency property from Paul Jenkins version of the BorderlessWindowBehavior.
 * https://bitbucket.org/aeoth/mahapps.metro/src/3c9392a42e71/MahApps.Metro/Behaviours/BorderlessWindowBehavior.cs
 * 
 */

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Shapes;
using System.Diagnostics;

namespace MetroToolkit
{
    /// <summary>
    /// Metro Styled Borderless Window Behavior
    /// </summary>
    public class BorderlessWindowBehavior : Behavior<Window>
    {
        #region Native Methods

        [StructLayout(LayoutKind.Sequential)]
        public struct MARGINS
        {
            public int leftWidth;
            public int rightWidth;
            public int topHeight;
            public int bottomHeight;
        }

        [DllImport("dwmapi.dll")]
        private static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMarInset);

        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int x;
            public int y;

            /// <summary>
            /// Initializes a new instance of the <see cref="POINT"/> struct.
            /// </summary>
            /// <param name="x">The x.</param>
            /// <param name="y">The y.</param>
            public POINT(int x, int y)
            {
                this.x = x;
                this.y = y;
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct MINMAXINFO
        {
            public POINT ptReserved;
            public POINT ptMaxSize;
            public POINT ptMaxPosition;
            public POINT ptMinTrackSize;
            public POINT ptMaxTrackSize;
        };

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public class MONITORINFO
        {
            public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
            public RECT rcMonitor = new RECT();
            public RECT rcWork = new RECT();
            public int dwFlags = 0;
        }
        
        /// <summary> Win32 </summary>
        [StructLayout(LayoutKind.Sequential, Pack = 0)]
        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
            public static readonly RECT Empty = new RECT();

            /// <summary>
            /// Gets the width. 
            /// </summary>
            public int Width
            {
                // Abs needed for BIDI OS
                get { return Math.Abs(right - left); }  
            }

            /// <summary>
            /// Gets the height.
            /// </summary>
            public int Height
            {
                get { return bottom - top; }
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="RECT"/> struct.
            /// </summary>
            /// <param name="left">The left.</param>
            /// <param name="top">The top.</param>
            /// <param name="right">The right.</param>
            /// <param name="bottom">The bottom.</param>
            public RECT(int left, int top, int right, int bottom)
            {
                this.left = left;
                this.top = top;
                this.right = right;
                this.bottom = bottom;
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="RECT"/> struct.
            /// </summary>
            /// <param name="rcSrc">The rc SRC.</param>
            public RECT(RECT rcSrc)
            {
                this.left = rcSrc.left;
                this.top = rcSrc.top;
                this.right = rcSrc.right;
                this.bottom = rcSrc.bottom;
            }

            /// <summary>
            /// Gets a value indicating whether this instance is empty.
            /// </summary>
            /// <value>
            ///   <c>true</c> if this instance is empty; otherwise, <c>false</c>.
            /// </value>
            public bool IsEmpty
            {
                get
                {
                    // BUGBUG : On Bidi OS (hebrew arabic) left > right
                    return left >= right || top >= bottom;
                }
            }

            /// <summary>
            /// Return a user friendly representation of this struct
            /// </summary>
            /// <returns>
            /// A <see cref="System.String"/> that represents this instance.
            /// </returns>
            public override string ToString()
            {
                if (this == RECT.Empty) 
                    return "RECT {Empty}";
                
                return String.Format("RECT {{ left : {0} / top : {1} / right : {2} / bottom : {3} }}", left, top, right, bottom);
            }

            /// <summary>
            /// Determine if 2 RECT are equal (deep compare)
            /// </summary>
            /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
            /// <returns>
            ///   <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
            /// </returns>
            public override bool Equals(object obj)
            {
                if (!(obj is Rect))
                    return false;

                return (this == (RECT)obj);
            }

            /// <summary>
            /// Return the HashCode for this struct (not guaranteed to be unique)
            /// </summary>
            /// <returns>
            /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
            /// </returns>
            public override int GetHashCode()
            {
                return left.GetHashCode() + top.GetHashCode() + right.GetHashCode() + bottom.GetHashCode();
            }

            /// <summary>
            /// Determine if 2 RECT are equal (deep compare)
            /// </summary>
            /// <param name="rect1">The rect1.</param>
            /// <param name="rect2">The rect2.</param>
            /// <returns>
            /// The result of the operator.
            /// </returns>
            public static bool operator ==(RECT rect1, RECT rect2)
            {
                return (rect1.left == rect2.left && rect1.top == rect2.top && rect1.right == rect2.right && rect1.bottom == rect2.bottom);
            }

            /// <summary>
            /// Determine if 2 RECT are different (deep compare)
            /// </summary>
            /// <param name="rect1">The rect1.</param>
            /// <param name="rect2">The rect2.</param>
            /// <returns>
            /// The result of the operator.
            /// </returns>
            public static bool operator !=(RECT rect1, RECT rect2)
            {
                return !(rect1 == rect2);
            }
        }

        /// <summary>
        /// Gets the monitor info.
        /// </summary>
        /// <param name="hMonitor">The h monitor.</param>
        /// <param name="lpmi">The lpmi.</param>
        /// <returns></returns>
        [DllImport("user32")]
        internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);

        /// <summary>
        /// Monitors from window.
        /// </summary>
        /// <param name="handle">The handle.</param>
        /// <param name="flags">The flags.</param>
        /// <returns></returns>
        [DllImport("User32")]
        internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);

        /// <summary>
        /// Wms the get min max info.
        /// </summary>
        /// <param name="hwnd">The HWND.</param>
        /// <param name="lParam">The l param.</param>
        private static void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam)
        {
            MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));

            // Adjust the maximized size and position to fit the work area of the correct monitor
            int MONITOR_DEFAULTTONEAREST = 0x00000002;
            System.IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);

            if (monitor != System.IntPtr.Zero)
            {
                MONITORINFO monitorInfo = new MONITORINFO();
                GetMonitorInfo(monitor, monitorInfo);
                RECT rcWorkArea = monitorInfo.rcWork;
                RECT rcMonitorArea = monitorInfo.rcMonitor;
                mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
                mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
                mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
                mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
            }

            Marshal.StructureToPtr(mmi, lParam, true);
        }

        /// <summary>
        /// Defs the window proc.
        /// </summary>
        /// <param name="hwnd">The HWND.</param>
        /// <param name="msg">The MSG.</param>
        /// <param name="wParam">The w param.</param>
        /// <param name="lParam">The l param.</param>
        /// <returns></returns>
        [DllImport("user32.dll")]
        public static extern IntPtr DefWindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);

        #endregion

        private const int WM_GETMINMAXINFO = 0x24;
        private const int WM_NCACTIVATE = 0x86;
        private const int WM_NCCALCSIZE = 0x83;
        private const int WM_NCPAINT = 0x85;

        private HwndSource m_hwndSource;
        private IntPtr m_hwnd;

        /// <summary>
        /// Called after the behavior is attached to an AssociatedObject.
        /// </summary>
        protected override void OnAttached()
        {
            if (AssociatedObject.IsInitialized)
                AddHwndHook();
            else
                AssociatedObject.SourceInitialized += AssociatedObject_SourceInitialized;

            AssociatedObject.WindowStyle = WindowStyle.None;
            AssociatedObject.ResizeMode = ResizeWithGrip ? ResizeMode.CanResizeWithGrip : ResizeMode.CanResize;

            base.OnAttached();
        }

        /// <summary>
        /// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
        /// </summary>
        protected override void OnDetaching()
        {
            RemoveHwndHook();
            base.OnDetaching();
        }

        /// <summary>
        /// Adds the HWND hook.
        /// </summary>
        private void AddHwndHook()
        {
            m_hwndSource = HwndSource.FromVisual(AssociatedObject) as HwndSource;
            m_hwndSource.AddHook(HwndHook);
            m_hwnd = new WindowInteropHelper(AssociatedObject).Handle;
        }

        /// <summary>
        /// Removes the HWND hook.
        /// </summary>
        private void RemoveHwndHook()
        {
            AssociatedObject.SourceInitialized -= AssociatedObject_SourceInitialized;
            m_hwndSource.RemoveHook(HwndHook);
        }

        /// <summary>
        /// Handles the SourceInitialized event of the AssociatedObject control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void AssociatedObject_SourceInitialized(object sender, EventArgs e)
        {
            AddHwndHook();
        }

        /// <summary>
        /// HWNDs the hook.
        /// </summary>
        /// <param name="hWnd">The h WND.</param>
        /// <param name="message">The message.</param>
        /// <param name="wParam">The w param.</param>
        /// <param name="lParam">The l param.</param>
        /// <param name="handled">if set to <c>true</c> [handled].</param>
        /// <returns></returns>
        private IntPtr HwndHook(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            IntPtr returnval = IntPtr.Zero;

            switch (message)
            {
                case WM_NCCALCSIZE:
					{
						// Hides the border
						handled = true;
					}
                    break;
                case WM_NCPAINT:
                    {
						// Works for Windows Vista and higher
                        if (Environment.OSVersion.Version.Major >= 6)
                        {
                            var m = new MARGINS { bottomHeight = 1, leftWidth = 1, rightWidth = 1, topHeight = 1 };
                            DwmExtendFrameIntoClientArea(m_hwnd, ref m);
                        }
                        handled = true;
                    }
                    break;
                case WM_NCACTIVATE:
                    {
                        /* As per http://msdn.microsoft.com/en-us/library/ms632633(VS.85).aspx , "-1" lParam does not
                         * repaint the nonclient area to reflect the state change. */
                        returnval = DefWindowProc(hWnd, message, wParam, new IntPtr(-1));
                        handled = true;
                    }
                    break;
                case WM_GETMINMAXINFO:
                    {
						/* From Lester's Blog (thanks @aeoth):  
						 * http://blogs.msdn.com/b/llobo/archive/2006/08/01/maximizing-window-_2800_with-windowstyle_3d00_none_2900_-considering-taskbar.aspx */
						WmGetMinMaxInfo(hWnd, lParam);
						handled = true;
					}
                    break;
            }

            return returnval;
        }

        public static DependencyProperty ResizeWithGripDependencyProperty = DependencyProperty.Register("ResizeWithGrip", typeof(bool), typeof(BorderlessWindowBehavior), new PropertyMetadata(true));

        /// <summary>
        /// Gets or sets a value indicating whether [resize with grip].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [resize with grip]; otherwise, <c>false</c>.
        /// </value>
        public bool ResizeWithGrip
        {
            get { return (bool)GetValue(ResizeWithGripDependencyProperty); }
            set { SetValue(ResizeWithGripDependencyProperty, value); }
        }
    }
}
